package org.kazao.sms;

import java.io.*;
import java.util.*;
import java.util.logging.*;
import java.text.*;

public class KazaoService {
    public static final String _name = "Kazao Sms Library";
    
    public static final String _version = "3.0";
    
    public static final String _reldate = "13 Maret 2007";
    
    private static Logger log = Logger.getLogger("org.kazao.sms");
    
    public static final int ERR_OK = 0;
    
    public static final int ERR_GENERIC_ERROR = -1;
    
    public static final int ERR_NOT_INITIALIZED = -10;
    
    public static final int ERR_NOT_CONNECTED = -11;
    
    public static final int ERR_COMM_NOT_SUPPORTED = -20;
    
    public static final int ERR_CHARSET_HEX_NOT_SUPPORTED = -21;
    
    public static final int ERR_CANNOT_DISABLE_INDICATIONS = -22;
    
    public static final int ERR_MESSAGE_NOT_FOUND = -30;
    
    public static final int ERR_SEND_FAILED = -40;
    
    public static final int ERR_PHONEBOOK_NOT_LOADED = -50;
    
    public static final int ERR_INVALID_DIR = -100;
    
    public static final int ERR_NO_CACHE = -101;
    
    public static final int ERR_SIM_PIN_ERROR =-102;
    
    public static final int ERR_NOT_SUPPORTED = -9999;
    
    public static final int MODE_ASCII = 1;
    
    public static final int MODE_PDU = 2;
    
    public static final int RECEIVE_MODE_SYNC = 1;
    public static final int RECEIVE_MODE_ASYNC = 2;
    
    public static final String DEFAULT_VALUE_NOT_REPORTED = "* N/A *";
    
    private static final int MAX_SMS_LEN_7BIT = 160;
    private static final int MAX_SMS_LEN_8BIT = 140;
    private static final int MAX_SMS_LEN_UNICODE = 70;
    private static final String SMS_SPLIT_SIGNATURE = "?$;";
    private static final int SMS_PARTS = 8;
    private static int smsSplitId = 0;
    
    private String cacheDir;
    private String smscNumber;
    private String simPin;
    private int operationMode;
    private int supportedModes;
    private int receiveMode;
    
    private KazaoSerialDriver serialDriver;
    private boolean initialized;
    private boolean connected;
    private KazaoPhoneBook phoneBook;
    private KazaoDeviceInfo deviceInfo;
    
    private CReceiveThread receiveThread;
    
    private Object _SYNC_ = new Object();
    
    private int TON;
    
    private int simCapacity = -1;
    private int simUsed = -1;
    private int phoneCapacity = -1;
    private int phoneUsed = -1;
    
    private int maxLengthNumber = -1;
    private int maxLengthName = -1;
    private int TON_NPI = -1;
    private KazaoOperator[] operator = new KazaoOperator[0];
    
    public KazaoService(String port, int baud) {
        setInitialized(false);
        setConnected(false);
        serialDriver = new KazaoSerialDriver(port, baud, log);
        phoneBook = new KazaoPhoneBook();
        deviceInfo = new KazaoDeviceInfo();
        receiveMode = RECEIVE_MODE_SYNC;
        receiveThread = new CReceiveThread();
        receiveThread.start();
        log.setLevel(Level.CONFIG);
    }
    
    public boolean getInitialized() {
        return initialized;
    }
    
    public boolean getConnected() {
        return connected;
    }
    
    public KazaoDeviceInfo getDeviceInfo() {
        return deviceInfo;
    }
    
    public int setCacheDir(String dir) {
        if (dir == null) {
            return ERR_INVALID_DIR;
        } else {
            File f = new File(dir);
            if (f.exists()) {
                cacheDir = dir;
                return ERR_OK;
            } else {
                return ERR_INVALID_DIR;
            }
        }
    }
    
    public void setSmscNumber(String smscNumber) {
        this.smscNumber = smscNumber;
    }
    
    public String getSmscNumber() {
        return smscNumber;
    }
    
    public void setSimPin(String simPin) {
        this.simPin = simPin;
    }
    
    public String getSimPin() {
        return simPin;
    }
    
    public boolean setOperationMode(int mode) {
        boolean result;
        
        try {
            switch (mode) {
                case MODE_ASCII:
                    serialDriver.send(KazaoATCommands.AT_ASCII_MODE);
                    if (serialDriver.getResponse().equalsIgnoreCase(KazaoATCommands.AT_OK)) {
                        result = true;
                        operationMode = MODE_ASCII;
                    } else {
                        result = false;
                    }
                    break;
                case MODE_PDU:
                    serialDriver.send(KazaoATCommands.AT_PDU_MODE);
                    if (serialDriver.getResponse().equalsIgnoreCase(KazaoATCommands.AT_OK)) {
                        result = true;
                        operationMode = MODE_PDU;
                    } else {
                        result = false;
                    }
                    break;
                case 0:
                    operationMode = 0;
                    result = true;
                    break;
                default:
                    result = false;
            }
        } catch (Exception e) {
            result = false;
        }
        return result;
    }
    
    public int getOperationMode() {
        return operationMode;
    }
    
    public void setReceiveMode(int receiveMode) {
        this.receiveMode = receiveMode;
    }
    
    public int getReceiveMode() {
        return receiveMode;
    }
    
    public String getCacheDir() {
        return cacheDir;
    }
    
    public int initialize() {
        cacheDir = null;
        smscNumber = null;
        simPin = null;
        operationMode = 0;
        supportedModes = 0;
        setInitialized(true);
        return ERR_OK;
    }
    
    public int connect() {
        synchronized (_SYNC_) {
            if (getInitialized()) {
                if (getCacheDir() == null) {
                    return ERR_NO_CACHE;
                } else {
                    try {
                        if (serialDriver.open()) {
                            try {
                                Thread.sleep(500);
                            } catch (Exception e) {
                            }
                            serialDriver.clearBuffer();
                            serialDriver.send(KazaoATCommands.AT_ECHO_OFF);
                            serialDriver.getResponse();
                            serialDriver.send(KazaoATCommands.AT_AT);
                            if (serialDriver.getResponse().equalsIgnoreCase(KazaoATCommands.AT_OK)) {
                                if (getSimPin() != null) {
                                    serialDriver.send(KazaoATCommands.AT_CHECK_LOGIN);
                                    if (serialDriver.getResponse().indexOf(KazaoATCommands.AT_READY) == -1) {
                                        if (getSimPin() == null) {
                                            serialDriver.close();
                                            setConnected(false);
                                            return ERR_SIM_PIN_ERROR;
                                        } else {
                                            serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_LOGIN, "{1}", getSimPin()));
                                            if (serialDriver.getResponse().indexOf(KazaoATCommands.AT_OK) == -1) {
                                                serialDriver.close();
                                                setConnected(false);
                                                return ERR_SIM_PIN_ERROR;
                                            } else {
                                                // Pin OK - wait 20 seconds for the GSM device to boot up...
                                                try {
                                                    Thread.sleep(10000);
                                                } catch (Exception e) {
                                                }
                                                serialDriver.send(KazaoATCommands.AT_AT); serialDriver.getResponse();
                                                try {
                                                    Thread.sleep(10000);
                                                } catch (Exception e) {
                                                }
                                                serialDriver.send(KazaoATCommands.AT_AT); serialDriver.getResponse();
                                            }
                                        }
                                    }
                                }
                                serialDriver.send(KazaoATCommands.AT_ASCII_MODE);
                                if (serialDriver.getResponse().equalsIgnoreCase(KazaoATCommands.AT_OK)) {
                                    supportedModes |= MODE_ASCII;
                                }
                                serialDriver.send(KazaoATCommands.AT_PDU_MODE);
                                if (serialDriver.getResponse().equalsIgnoreCase(KazaoATCommands.AT_OK)) {
                                    supportedModes |= MODE_PDU;
                                }
                                if ((supportedModes & MODE_PDU) != 0) {
                                    setOperationMode(MODE_PDU);
                                } else if ((supportedModes & MODE_ASCII) != 0) {
                                    setOperationMode(MODE_ASCII);
                                } else {
                                    setOperationMode(0);
                                }
                                if (operationMode == 0) {
                                    serialDriver.close();
                                    setConnected(false);
                                    return ERR_COMM_NOT_SUPPORTED;
                                } else {
                                    serialDriver.send(KazaoATCommands.AT_DISABLE_INDICATIONS);
                                    if (serialDriver.getResponse().equalsIgnoreCase(KazaoATCommands.AT_OK)) {
                                        if (operationMode == MODE_ASCII) {
                                            serialDriver.send(KazaoATCommands.AT_CHARSET_HEX);
                                            if (serialDriver.getResponse().equalsIgnoreCase(KazaoATCommands.AT_OK)) {
                                                setConnected(true);
                                                refreshDeviceInfo();
                                                if (deviceInfo.getManufacturer().toUpperCase().indexOf("SIEMENS") >= 0) {
                                                    serialDriver.send(KazaoATCommands.AT_SMS_STORAGE);
                                                    serialDriver.getResponse();
                                                }
                                            } else {
                                                serialDriver.close();
                                                setConnected(false);
                                                return ERR_CHARSET_HEX_NOT_SUPPORTED;
                                            }
                                        } else {
                                            setConnected(true);
                                            refreshDeviceInfo();
                                            if (deviceInfo.getManufacturer().toUpperCase().indexOf("SIEMENS") >= 0) {
                                                serialDriver.send(KazaoATCommands.AT_SMS_STORAGE);
                                                serialDriver.getResponse();
                                            }
                                        }
                                    } else {
                                        serialDriver.close();
                                        setConnected(false);
                                        return ERR_CANNOT_DISABLE_INDICATIONS;
                                    }
                                }
                            } else {
                                serialDriver.close();
                                setConnected(false);
                            }
                        } else {
                            setConnected(false);
                        }
                        return (getConnected() ? ERR_OK : ERR_NOT_CONNECTED);
                    } catch (Exception e) {
                        serialDriver.close();
                        return ERR_NOT_CONNECTED;
                    }
                }
            } else {
                return ERR_NOT_INITIALIZED;
            }
        }
    }
    
    public int disconnect() {
        synchronized (_SYNC_) {
            try {
                serialDriver.close();
            } catch (Exception e) {
            }
            setConnected(false);
            return ERR_OK;
        }
    }
    
    public int refreshDeviceInfo() {
        synchronized (_SYNC_) {
            if (getConnected()) {
                try {
                    deviceInfo.manufacturer = getManufacturer();
                    deviceInfo.model = getModel();
                    deviceInfo.serialNo = getSerialNo();
                    deviceInfo.imsi = getImsi();
                    deviceInfo.swVersion = getSwVersion();
                    deviceInfo.batteryLevel = getBatteryLevel();
                    deviceInfo.signalLevel = getSignalLevel();
                    return ERR_OK;
                } catch (Exception e) {
                    setConnected(false);
                    return ERR_NOT_CONNECTED;
                }
            } else {
                return ERR_NOT_CONNECTED;
            }            
        }
    }
    
    public int readMessages(LinkedList messageList, int messageClass) {
        int i, j, memIndex;
        String response, line, sms, temp, originator, text, pdu;
        String day, month, year, hour, min, sec;
        BufferedReader reader;
        Date sentDate;
        Calendar cal = Calendar.getInstance();
        
        synchronized (_SYNC_) {
            if (getConnected()) {
                switch (operationMode) {
                    case MODE_ASCII:
                        try {
                            serialDriver.send(KazaoATCommands.AT_CMD_MODE);
                            messageList.clear();
                            serialDriver.send(KazaoATCommands.AT_ASCII_MODE);
                            response = serialDriver.getResponse();
                            serialDriver.send(KazaoATCommands.AT_CHARSET_HEX);
                            response = serialDriver.getResponse();
                            switch (messageClass) {
                                case KazaoIncomingMessage.CLASS_ALL:
                                    serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_LIST, "{1}", "\"ALL\""));
                                    break;
                                case KazaoIncomingMessage.CLASS_REC_UNREAD:
                                    serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_LIST, "{1}", "\"REC UNREAD\""));
                                    break;
                                case KazaoIncomingMessage.CLASS_REC_READ:
                                    return ERR_NOT_SUPPORTED;
                                case KazaoIncomingMessage.CLASS_STO_UNSENT:
                                    return ERR_NOT_SUPPORTED;
                                case KazaoIncomingMessage.CLASS_STO_SENT:
                                    return ERR_NOT_SUPPORTED;
                            }
                            response = serialDriver.getResponse();
                            
                            sms = "";
                            reader = new BufferedReader(new StringReader(response));
                            line = reader.readLine();
                            while ((line != null) && (!line.equalsIgnoreCase("OK"))) {
                                if (line.indexOf("+CMGL") == 0) {
                                    sms = line;
                                } else if ((line.indexOf("+CMGL") == -1) && (line.length() > 0)) {
                                    sms = sms + "~" + line;
                                } else {
                                    sms = "";
                                }
                                if ((sms.indexOf("\"REC READ\"") > -1) || (sms.indexOf("\"REC UNREAD\"") > -1)) {
                                    if (sms.indexOf("~") != -1) {
                                        i = 6;
                                        temp = "";
                                        while (sms.charAt(i) != ',') {
                                            temp += sms.charAt(i);
                                            i++;
                                        }
                                        memIndex = Integer.parseInt(temp.trim());
                                        i = sms.indexOf('"') + 1;
                                        i = sms.indexOf('"', i) + 1;
                                        i = sms.indexOf('"', i) + 1;
                                        temp = "";
                                        while (sms.charAt(i) != '"') {
                                            temp += sms.charAt(i);
                                            i++;
                                        }
                                        originator = temp;
                                        
                                        i++;
                                        i = sms.indexOf('"', i) + 1;
                                        day = sms.substring(i + 6, i + 6 + 2);
                                        month = sms.substring(i + 3, i + 3 + 2);
                                        year = sms.substring(i, i + 2); year = "20" + year;
                                        hour = sms.substring(i + 9, i + 9 + 2);
                                        min = sms.substring(i + 12, i + 12 + 2);
                                        sec = sms.substring(i + 15, i + 15 + 2);
                                        cal.set(Integer.parseInt(year), Integer.parseInt(month) - 1, Integer.parseInt(day), Integer.parseInt(hour), Integer.parseInt(min), Integer.parseInt(sec));
                                        sentDate = cal.getTime();
                                        
                                        i = sms.indexOf('~') + 1;
                                        text = sms.substring(i);
                                        
                                        messageList.add(new KazaoIncomingMessage(sentDate, originator, KazaoGSMAlphabets.hex2Text(text, KazaoGSMAlphabets.GSM7BITDEFAULT), memIndex));
                                        deviceInfo.getStatistics().incTotalIn();
                                    }
                                } else if (sms.indexOf("\"STO SENT\"") > -1) {
                                    
                                } else if (sms.indexOf("\"STO UNSENT\"") > -1) {
                                    
                                }
                                line = reader.readLine();
                            }
                            reader.close();
                            return ERR_OK;
                        } catch (Exception e) {
                            e.printStackTrace();
                            return ERR_GENERIC_ERROR;
                        }
                    case MODE_PDU:
                        try {
                            serialDriver.send(KazaoATCommands.AT_CMD_MODE);
                            messageList.clear();
                            serialDriver.send(KazaoATCommands.AT_PDU_MODE);
                            response = serialDriver.getResponse();
                            switch (messageClass) {
                                case KazaoIncomingMessage.CLASS_ALL:
                                    serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_LIST, "{1}", "4"));
                                    break;
                                case KazaoIncomingMessage.CLASS_REC_UNREAD:
                                    serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_LIST, "{1}", "0"));
                                    break;
                                case KazaoIncomingMessage.CLASS_REC_READ:
                                    serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_LIST, "{1}", "1"));
                                    break;
                                case KazaoIncomingMessage.CLASS_STO_UNSENT:
                                    serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_LIST, "{1}", "2"));
                                    break;
                                case KazaoIncomingMessage.CLASS_STO_SENT:
                                    serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_LIST, "{1}", "3"));
                                    break;
                            }
                            response = serialDriver.getResponse();
                            reader = new BufferedReader(new StringReader(response));
                            line = reader.readLine().trim();
                            while ((line != null) && (line.length() > 0) && (!line.equalsIgnoreCase("OK"))) {
                                i = line.indexOf(':');
                                j = line.indexOf(',');
                                memIndex = Integer.parseInt(line.substring(i + 1, j).trim());
                                pdu = reader.readLine();
                                if (isIncomingMessage(pdu)) {
                                    messageList.add(new KazaoIncomingMessage(pdu, memIndex));
                                    deviceInfo.getStatistics().incTotalIn();
                                }
                                //else messageList.add(new KazaoOutgoingMessage(pdu, memIndex));
                                line = reader.readLine().trim();
                            }
                            reader.close();
                            return ERR_OK;
                        } catch (Exception e) {
                            e.printStackTrace();
                            return ERR_GENERIC_ERROR;
                        }
                    default:
                        return ERR_GENERIC_ERROR;
                }
            } else {
                return ERR_NOT_CONNECTED;
            }
        }
    }
    
    public int sendMessage(KazaoOutgoingMessage message) {
        LinkedList messageList;
        KazaoOutgoingMessage msg;
        int error;
        
        synchronized (_SYNC_) {
            messageList = new LinkedList();
            messageList.add(message);
            error = sendMessage(messageList);
            if (error == ERR_OK) {
                msg = (KazaoOutgoingMessage) messageList.get(0);
                message.setDispatchDate(msg.getDispatchDate());
            }
            return error;
        }
    }
    
    public int sendMessage(LinkedList messageList) {
        LinkedList outList;
        KazaoOutgoingMessage message;
        String response, pdu;
        int i, j, error;
        
        synchronized (_SYNC_) {
            if (getConnected()) {
                outList = messageList;
                switch (operationMode) {
                    case MODE_ASCII:
                        try {
                            serialDriver.send(KazaoATCommands.AT_ASCII_MODE);
                            response = serialDriver.getResponse();
                            serialDriver.send(KazaoATCommands.AT_KEEP_LINK_OPEN);
                            response = serialDriver.getResponse();
                            serialDriver.send(KazaoATCommands.AT_CHARSET_HEX);
                            response = serialDriver.getResponse();
                            error = ERR_OK;
                            for (i = 0; i < outList.size(); i ++) {
                                message = (KazaoOutgoingMessage) outList.get(i);
                                serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_SEND_MESSAGE, "{1}", message.getRecipient()));
                                serialDriver.skipBytes(4);
                                serialDriver.send(message.getHexText());
                                serialDriver.send((char) 26);
                                response = serialDriver.getResponse();
                                if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                                    message.setDispatchDate(new Date());
                                    deviceInfo.getStatistics().incTotalOut();
                                } else {
                                    message.setDispatchDate(null);
                                    error = ERR_SEND_FAILED;
                                }
                            }
                            return error;
                        } catch (Exception e) {
                            e.printStackTrace();
                            setConnected(false);
                            return ERR_NOT_CONNECTED;
                        }
                    case MODE_PDU:
                        try {
                            serialDriver.send(KazaoATCommands.AT_PDU_MODE);
                            response = serialDriver.getResponse();
                            serialDriver.send(KazaoATCommands.AT_KEEP_LINK_OPEN);
                            response = serialDriver.getResponse();
                            error = ERR_OK;
                            for (i = 0; i < outList.size(); i ++) {
                                message = (KazaoOutgoingMessage) outList.get(i);
                                pdu = message.getPDU(smscNumber);
                                j = pdu.length();
                                j /= 2;
                                if (smscNumber == null) {
                                    
                                } else if (smscNumber.length() == 0) {
                                    j--;
                                } else {
                                    j -= ((smscNumber.length() - 1) / 2);
                                    j -= 2;
                                }
                                serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_SEND_MESSAGE, "\"{1}\"", "" + j));
                                serialDriver.skipBytes(4);
                                serialDriver.send(pdu);
                                serialDriver.send((char) 26);
                                response = serialDriver.getResponse();
                                if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                                    message.setDispatchDate(new Date());
                                    deviceInfo.getStatistics().incTotalOut();
                                } else {
                                    message.setDispatchDate(null);
                                    error = ERR_SEND_FAILED;
                                }
                            }
                            return error;
                        } catch (Exception e) {
                            e.printStackTrace();
                            setConnected(false);
                            return ERR_NOT_CONNECTED;
                        }
                    default:
                        return ERR_SEND_FAILED;
                }
            } else {
                return ERR_NOT_CONNECTED;
            }
        }
    }
    
    public int deleteMessage(KazaoIncomingMessage message) {
        synchronized (_SYNC_) {
            return deleteMessage(message.getMemIndex());
        }
    }
    
    public int deleteMessage(int memIndex) {
        String response;
        
        synchronized (_SYNC_) {
            if (getConnected()) {
                if (memIndex > 0) {
                    try {
                        serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_DELETE_MESSAGE, "{1}", "" + memIndex));
                        response = serialDriver.getResponse();
                        if (response.indexOf(KazaoATCommands.AT_OK) > -1) return ERR_OK;
                        else return ERR_MESSAGE_NOT_FOUND;
                    } catch (Exception e) {
                        e.printStackTrace();
                        return ERR_GENERIC_ERROR;
                    }
                } else {
                    return ERR_GENERIC_ERROR;
                }
            } else {
                return ERR_NOT_CONNECTED;
            }
        }
    }
    
    public boolean received(KazaoIncomingMessage message) {
        return false;
    }
    
    private boolean isIncomingMessage(String pdu) {
        String str;
        int index, i;
        
        str = pdu.substring(0, 2);
        i = Integer.parseInt(str, 16);
        index = (i + 1) * 2;
        
        str = pdu.substring(index, index + 2);
        i = Integer.parseInt(str, 16);
        if ((i & 0x0B) == 0) {
            return true;
        } else {
            return false;
        }
    }
    
    private void setConnected(boolean connected) {
        this.connected = connected;
    }
    private void setInitialized(boolean initialized) {
        this.initialized = initialized;
    }
    
    private String getManufacturer() throws Exception {
        String response;
        
        serialDriver.send(KazaoATCommands.AT_MANUFACTURER);
        response = serialDriver.getResponse();
        if (response.length() == 0) {
            response = "";
        } else if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
            response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
            response = KazaoUtils.substituteSymbol(response, "\r", "");
            response = KazaoUtils.substituteSymbol(response, "\n", "");
        } else {
            response = DEFAULT_VALUE_NOT_REPORTED;
        }
        return response;
    }
    
    private String getModel() throws Exception {
        String response;
        
        serialDriver.send(KazaoATCommands.AT_MODEL);
        response = serialDriver.getResponse();
        if (response.length() == 0) {
            response = "";
        } else if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
            response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
            response = KazaoUtils.substituteSymbol(response, "\r", "");
            response = KazaoUtils.substituteSymbol(response, "\n", "");
        } else {
            response = DEFAULT_VALUE_NOT_REPORTED;
        }
        return response;
    }
    
    private String getSerialNo() throws Exception {
        String response;
        
        serialDriver.send(KazaoATCommands.AT_SERIALNO);
        response = serialDriver.getResponse();
        if (response.length() == 0) {
            response = "";
        } else if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
            response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
            response = KazaoUtils.substituteSymbol(response, "\r", "");
            response = KazaoUtils.substituteSymbol(response, "\n", "");
        } else {
            response = DEFAULT_VALUE_NOT_REPORTED;
        }
        return response;
    }
    
    private String getImsi() throws Exception {
        String response;
        
        serialDriver.send(KazaoATCommands.AT_IMSI);
        response = serialDriver.getResponse();
        if (response.length() == 0) {
            response = "";
        } else if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
            response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
            response = KazaoUtils.substituteSymbol(response, "\r", "");
            response = KazaoUtils.substituteSymbol(response, "\n", "");
        } else {
            response = DEFAULT_VALUE_NOT_REPORTED;
        }
        return response;
    }
    
    private String getSwVersion() throws Exception {
        String response;
        
        serialDriver.send(KazaoATCommands.AT_SOFTWARE);
        response = serialDriver.getResponse();
        if (response.length() == 0) {
            response = "";
        } else if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
            response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
            response = KazaoUtils.substituteSymbol(response, "\r", "");
            response = KazaoUtils.substituteSymbol(response, "\n", "");
        } else {
            response = DEFAULT_VALUE_NOT_REPORTED;
        }
        return response;
    }
    
    private int getBatteryLevel() throws Exception {
        String response, level;
        StringTokenizer tokens;
        
        serialDriver.send(KazaoATCommands.AT_BATTERY);
        response = serialDriver.getResponse();
        if (response.length() == 0) {
            level = "0";
        } else if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
            response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
            response = KazaoUtils.substituteSymbol(response, "\r", "");
            response = KazaoUtils.substituteSymbol(response, "\n", "");
            tokens = new StringTokenizer(response, ":,");
            tokens.nextToken();
            tokens.nextToken();
            level = tokens.nextToken().trim();
        } else {
            level = "0";
        }
        return (Integer.parseInt(level));
    }
    
    private int getSignalLevel() throws Exception {
        String response, level;
        StringTokenizer tokens;
        
        serialDriver.send(KazaoATCommands.AT_SIGNAL);
        response = serialDriver.getResponse();
        if (response.length() == 0) {
            level = "0";
        } else if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
            response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
            response = KazaoUtils.substituteSymbol(response, "\r", "");
            response = KazaoUtils.substituteSymbol(response, "\n", "");
            tokens = new StringTokenizer(response, ":,");
            tokens.nextToken();
            level = tokens.nextToken().trim();
        } else {
            level = "0";
        }
        return (Integer.parseInt(level) * 100 / 31);
    }
    
    private LinkedList splitLargeMessages(LinkedList messageList) {
        LinkedList outList;
        KazaoOutgoingMessage message, newMessage;
        int i, splitIndex, messageLength, smsSplitId, smsDispatchId;
        int maxMessageLength, partLength;
        String splitText;
        
        outList = new LinkedList();
        for (i = 0; i < messageList.size(); i ++) {
            message = (KazaoOutgoingMessage) messageList.get(i);
            switch (message.getMessageEncoding()) {
                case KazaoMessage.MESSAGE_ENCODING_7BIT:
                    maxMessageLength = MAX_SMS_LEN_7BIT;
                    break;
                case KazaoMessage.MESSAGE_ENCODING_8BIT:
                    maxMessageLength = MAX_SMS_LEN_8BIT;
                    break;
                case KazaoMessage.MESSAGE_ENCODING_UNICODE:
                    maxMessageLength = MAX_SMS_LEN_UNICODE;
                    break;
                default:
                    maxMessageLength = 0;
                    break;
            }
            messageLength = message.getText().length();
            if (messageLength > maxMessageLength) {
                partLength = maxMessageLength - SMS_SPLIT_SIGNATURE.length();
                smsSplitId = getSmsSplitId();
                splitIndex = 0;
                while ((splitIndex * partLength) < messageLength) {
                    int from = splitIndex * partLength;
                    int to = from + partLength;
                    if (to > messageLength) {
                        to = messageLength;
                    }
                    splitText = message.getText().substring(from, to);
                    smsDispatchId = smsSplitId + splitIndex;
                    splitText = SMS_SPLIT_SIGNATURE.substring(0, 1) + (char) smsDispatchId + SMS_SPLIT_SIGNATURE.substring(2, 3) + splitText;
                    newMessage = new KazaoOutgoingMessage(message.getRecipient(), splitText);
                    newMessage.setMessageEncoding(message.getMessageEncoding());
                    outList.add(newMessage);
                    splitIndex ++;
                }
            } else outList.add(message);
        }
        return outList;
    }
    
    private int getSmsSplitId() {
        int id;
        
        id = smsSplitId;
        smsSplitId ++;
        if (smsSplitId > 15) {
            smsSplitId = 0;
        }
        return id << 3;
    }
    
    private class CReceiveThread extends Thread {
        public void run() {
            LinkedList messageList;
            
            messageList = new LinkedList();
            while (true) {
                try {
                    sleep(10000);
                } catch (Exception e) {
                }
                synchronized (_SYNC_) {
                    if ((getConnected()) && (getReceiveMode() == RECEIVE_MODE_ASYNC)) {
                        messageList.clear();
                        readMessages(messageList, KazaoIncomingMessage.CLASS_ALL);
                        for (int i = 0; i < messageList.size(); i ++) {
                            KazaoIncomingMessage message = (KazaoIncomingMessage) messageList.get(i);
                            if (received(message)) {
                                deleteMessage(message);
                            }
                        }
                    }
                }
            }
        }
    }
    /**
     *  Tampilakan log atau tidak
     */
    public void setLogging(boolean log) {
        if (log) {
            this.log.setLevel(Level.CONFIG);
        } else {
            this.log.setLevel(Level.OFF);
        }
    }
    
    public void initPhonebook() {
        String response;
        StringTokenizer tokens;
        try {
            serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_WRITE_PHONEBOOK, "{1}", "?"));
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
                response = KazaoUtils.substituteSymbol(response, "\r", "");
                response = KazaoUtils.substituteSymbol(response, "\n", "");
                tokens = new StringTokenizer(response, ":(,-)");
                tokens.nextToken();tokens.nextToken();tokens.nextToken();tokens.nextToken();
                maxLengthNumber = Integer.parseInt(tokens.nextToken());
                TON_NPI = Integer.parseInt(tokens.nextToken());
                tokens.nextToken();
                maxLengthName = Integer.parseInt(tokens.nextToken());
            }
        } catch (Exception e) {
        }
    }
    
    public int getMaxLengthNumber() {
        if (maxLengthNumber == -1) {
            initPhonebook();
        }
        return maxLengthNumber;
    }
    
    public int getMaxLengthName() {
        if (maxLengthName == -1) {
            initPhonebook();
        }
        return maxLengthName;
    }
    
    public int getTONNPI() {
        if (TON_NPI == -1) {
            initPhonebook();
        }
        return TON_NPI;
    }
    
    public void initCapacity() {
        String response;
        StringTokenizer tokens;
        try {
            serialDriver.send(KazaoATCommands.AT_SELECT_SIM);
            response = serialDriver.getResponse();
            serialDriver.send(KazaoATCommands.AT_GET_CAPACITY);
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
                response = KazaoUtils.substituteSymbol(response, "\r", "");
                response = KazaoUtils.substituteSymbol(response, "\n", "");
                tokens = new StringTokenizer(response, ":,");
                tokens.nextToken(); tokens.nextToken();
                simUsed = Integer.parseInt(tokens.nextToken());
                simCapacity = Integer.parseInt(tokens.nextToken());
            }
            serialDriver.send(KazaoATCommands.AT_SELECT_PHONE);
            response = serialDriver.getResponse();
            serialDriver.send(KazaoATCommands.AT_GET_CAPACITY);
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
                response = KazaoUtils.substituteSymbol(response, "\r", "");
                response = KazaoUtils.substituteSymbol(response, "\n", "");
                tokens = new StringTokenizer(response, ":,");
                tokens.nextToken(); tokens.nextToken();
                phoneUsed = Integer.parseInt(tokens.nextToken());
                phoneCapacity = Integer.parseInt(tokens.nextToken());
            }
        } catch (Exception e) {
        }
    }
    
    public int getSimCapacity() {
        if (simCapacity == -1) {
            initCapacity();
        }
        return simCapacity;
    }
    
    public int getSimUsed() {
        if (simUsed == -1) {
            initCapacity();
        }
        return simUsed;
    }
    
    public int getPhoneCapacity() {
        if (phoneCapacity == -1) {
            initCapacity();
        }
        return phoneCapacity;
    }
    
    public int getPhoneUsed() {
        if (phoneUsed == -1) {
            initCapacity();
        }
        return phoneUsed;
    }
    
    public boolean deletePhonebook(String location, int index) {
        String response;
        boolean result = false;
        try {
            if (location.toUpperCase().trim().equals("SM")) {
                serialDriver.send(KazaoATCommands.AT_SELECT_SIM);
            }
            if (location.toUpperCase().trim().equals("ME")) {
                serialDriver.send(KazaoATCommands.AT_SELECT_PHONE);
            }
            response = serialDriver.getResponse();
            serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_WRITE_PHONEBOOK,  "{1}", ""+index));
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                result = true;
            }
        } catch (Exception e) {
        }
        return result;
    }
    
    public boolean addPhonebook(String location, int index, KazaoPhoneBook book) {
        String response;
        boolean result = false;
        try {
            if (location.toUpperCase().trim().equals("SM")) {
                serialDriver.send(KazaoATCommands.AT_SELECT_SIM);
            }
            if (location.toUpperCase().trim().equals("ME")) {
                serialDriver.send(KazaoATCommands.AT_SELECT_PHONE);
            }
            response = serialDriver.getResponse();
            if (index == -1) {
                serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_WRITE_PHONEBOOK, "{1}", ",\""+book.getNumber()+"\","+getTONNPI()+",\""+book.getName()+"\""));
            } else {
                serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_WRITE_PHONEBOOK, "{1}", index+",\""+book.getNumber()+"\","+getTONNPI()+",\""+book.getName()+"\""));
            }
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                result = true;
            }
        } catch (Exception e) {
        }
        return result;
    }
    
    public boolean addPhonebookInSim(KazaoPhoneBook book) {
        return addPhonebook("SM", -1, book);
    }
    
    public boolean addPhonebookInPhone(KazaoPhoneBook book) {
        return addPhonebook("ME", -1, book);
    }
    
    public boolean setPhonebookInSim(int index, KazaoPhoneBook book) {
        return addPhonebook("SM", index, book);
    }
    
    public boolean setPhonebookInPhone(int index, KazaoPhoneBook book) {
        return addPhonebook("ME", index, book);
    }
    
    public KazaoPhoneBook getPhonebook(String location, int index) {
        String response;
        StringTokenizer tokens;
        KazaoPhoneBook result = new KazaoPhoneBook(0, "", "");
        try {
            if (location.toUpperCase().trim().equals("SM")) {
                serialDriver.send(KazaoATCommands.AT_SELECT_SIM);
            }
            if (location.toUpperCase().trim().equals("ME")) {
                serialDriver.send(KazaoATCommands.AT_SELECT_PHONE);
            }
            response = serialDriver.getResponse();
            serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_READ_PHONEBOOK,  "{1}", ""+index));
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
                response = KazaoUtils.substituteSymbol(response, "\r", "");
                response = KazaoUtils.substituteSymbol(response, "\n", "");
                tokens = new StringTokenizer(response, ",");
                tokens.nextToken();
                String number = tokens.nextToken();
                number = number.substring(1, number.length()-1);
                tokens.nextToken();
                int address = index;
                String name = tokens.nextToken();
                name = name.substring(1, name.length()-1);
                result.setNumber(number);
                result.setAddress(address);
                result.setName(name);
            }
        } catch (Exception e) {
        }
        return result;
    }
    
    public KazaoPhoneBook getPhonebookInSim(int index) {
        return getPhonebook("SM", index);
    }
    
    public KazaoPhoneBook getPhonebookInPhone(int index) {
        return getPhonebook("ME", index);
    }
    
    public KazaoPhoneBook[] getPhonebookAll(String location) {
        int capacity = 0;
        if (location.toUpperCase().trim().equals("SM")) {
            capacity = getSimCapacity();
        }
        if (location.toUpperCase().trim().equals("ME")) {
            capacity = getPhoneCapacity();
        }
        KazaoPhoneBook[] result = new KazaoPhoneBook[capacity];
        for (int i=0; i < capacity; i++) {
            KazaoPhoneBook book = getPhonebook(location,  i+1);
            result[i] = new KazaoPhoneBook(book.getAddress(), book.getNumber(), book.getName());
        }
        
        return result;
    }
    
    public KazaoPhoneBook[] getPhonebookAllInSim() {
        return getPhonebookAll("SM");
    }
    
    public KazaoPhoneBook[] getPhonebookAllInPhone() {
        return getPhonebookAll("ME");
    }
    
    public void getAvailableOperator() {
        String response;
        StringTokenizer tokens;
        Vector opr = new Vector();
        try {
            serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_OPERATOR, "{1}", "?"));
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
                response = KazaoUtils.substituteSymbol(response, "\r", "");
                response = KazaoUtils.substituteSymbol(response, "\n", "");
                tokens = new StringTokenizer(response, ":(,)");
                tokens.nextToken();
                tokens.nextToken();
                while (tokens.hasMoreTokens()) {
                    int access = Integer.parseInt(tokens.nextToken());
                    String name = tokens.nextToken();
                    if (name.length() > 2) {
                        name = name.substring(1, name.length()-1);
                    }
                    String code = tokens.nextToken();
                    if (code.length() > 2) {
                        code = code.substring(1, code.length()-1);
                    }
                    //System.out.println(access+":"+name+":"+code);
                    if (code.length() == 5) {
                        opr.add(new KazaoOperator(access, name, code));
                    } else {
                        break;
                    }
                }
            }
        } catch (Exception e) {
        }
        KazaoOperator result[] = new KazaoOperator[opr.size()];
        try {
            for (int i=0; i < opr.size(); i++) {
                result[i] = (KazaoOperator) opr.get(i);
            }
        } catch (Exception e) {
        }
        operator = result;
    }
    
    public KazaoOperator[] getListOperator() {
        if (operator.length == 0) {
            getAvailableOperator();
        }
        return operator;
    }
    
    public KazaoOperator[] getListOperator(boolean refresh) {
        if (refresh) {
            getAvailableOperator();
        }
        return operator;
    }
    
    public KazaoOperator getCurrentOperator() {
        if (operator.length == 0) {
            getAvailableOperator();
        }
        KazaoOperator result = new KazaoOperator(2, "", "");
        for (int i=0; i < operator.length; i++) {
            if (operator[i].getAccess() == 2) {
                result.setAccess(operator[i].getAccess());
                result.setName(operator[i].getName());
                result.setCode(operator[i].getCode());
                break;
            }
        }
        return result;
    }
    
    public boolean registerOperator(KazaoOperator opr) {
        String response;
        boolean result = false;
        try {
            serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_OPERATOR, "{1}", "1,2,"+opr.getCode()));
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                result = true;
            }
        } catch (Exception e) {
        }
        return result;
    }
    
    public boolean registerOperator() {
        String response;
        boolean result = false;
        try {
            serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_OPERATOR, "{1}", "0"));
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                result = true;
            }
        } catch (Exception e) {
        }
        return result;
    }
    
    public String getSmsc() {
        String response;
        StringTokenizer tokens;
        String result = "";
        try {
            serialDriver.send(KazaoATCommands.AT_GET_SMSC);
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                response = KazaoUtils.substituteSymbol(response, KazaoATCommands.AT_OK, "");
                response = KazaoUtils.substituteSymbol(response, "\r", "");
                response = KazaoUtils.substituteSymbol(response, "\n", "");
                tokens = new StringTokenizer(response, ": ,");
                tokens.nextToken();
                result = tokens.nextToken();
                result = result.substring(1, result.length()-1);
            }
        } catch (Exception e) {
        }
        return result;
    }
    
    public boolean setSmsc(String smsc) {
        String response;
        boolean result = false;
        try {
            serialDriver.send(KazaoUtils.substituteSymbol(KazaoATCommands.AT_SET_SMSC, "{1}", smsc));
            response = serialDriver.getResponse();
            if (response.indexOf(KazaoATCommands.AT_OK) > -1) {
                result = true;
            }
        } catch (Exception e) {
        }
        return result;
    }
}
